/*
 * Copyright (c)  2008 Sebastien Tremblay
 *
 * id3-tag-builder - helpers.CheckTreeSelectionModel.java
 *
 * All source and documentation is copyrighted by Sebastien Tremblay
 * (seb at ryders dot net) and made available under the Apache License 2.0.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * $Id: CheckTreeSelectionModel.java 30 2010-01-08 04:01:06Z ryders $
 */
package com.empireone.id3tagbuilder.gui.tree;

import java.util.ArrayList;
import java.util.Stack;
import javax.swing.tree.DefaultTreeSelectionModel;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;

/**
 *
 * @author Sebastien Tremblay
 */
public class CheckTreeSelectionModel extends DefaultTreeSelectionModel {

    private TreeModel model;

    /**
     *
     * @param model
     */
    public CheckTreeSelectionModel(TreeModel model) {
	this.model = model;
	setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);
    }

    // tests whether there is any unselected node in the subtree of given path
    /**
     *
     * @param path
     * @return
     */
    public boolean isPartiallySelected(TreePath path) {
	if (isPathSelected(path, true)) {
	    return false;
	}
	TreePath[] selectionPaths = getSelectionPaths();
	if (selectionPaths == null) {
	    return false;
	}
	for (int j = 0; j < selectionPaths.length; j++) {
	    if (isDescendant(selectionPaths[j], path)) {
		return true;
	    }
	}
	return false;
    }

    // tells whether given path is selected.
    // if dig is true, then a path is assumed to be selected, if
    // one of its ancestor is selected.
    /**
     *
     * @param path
     * @param dig
     * @return
     */
    public boolean isPathSelected(TreePath path, boolean dig) {
	if (!dig) {
	    return super.isPathSelected(path);
	}
	while (path != null && !super.isPathSelected(path)) {
	    path = path.getParentPath();
	}
	return path != null;
    }

    // is path1 descendant of path2
    private boolean isDescendant(TreePath path1, TreePath path2) {
	Object obj1[] = path1.getPath();
	Object obj2[] = path2.getPath();
	for (int i = 0; i < obj2.length; i++) {
	    if (obj1[i] != obj2[i]) {
		return false;
	    }
	}
	return true;
    }

    /**
     *
     * @param pPaths
     */
    @Override
    public void setSelectionPaths(TreePath[] pPaths) {
	throw new UnsupportedOperationException("not implemented yet!!!");
    }

    /**
     *
     * @param paths
     */
    @Override
    public void addSelectionPaths(TreePath[] paths) {
	// unselect all descendants of paths[]
	for (int i = 0; i < paths.length; i++) {
	    TreePath path = paths[i];
	    TreePath[] selectionPaths = getSelectionPaths();
	    if (selectionPaths == null) {
		break;
	    }
	    ArrayList toBeRemoved = new ArrayList();
	    for (int j = 0; j < selectionPaths.length; j++) {
		if (isDescendant(selectionPaths[j], path)) {
		    toBeRemoved.add(selectionPaths[j]);
		}
	    }
	    super.removeSelectionPaths((TreePath[]) toBeRemoved.toArray(new TreePath[0]));
	}

	// if all siblings are selected then unselect them and select parent recursively
	// otherwize just select that path.
	for (int i = 0; i < paths.length; i++) {
	    TreePath path = paths[i];
	    TreePath temp = null;
	    while (areSiblingsSelected(path)) {
		temp = path;
		if (path.getParentPath() == null) {
		    break;
		}
		path = path.getParentPath();
	    }
	    if (temp != null) {
		if (temp.getParentPath() != null) {
		    addSelectionPath(temp.getParentPath());
		} else {
		    if (!isSelectionEmpty()) {
			removeSelectionPaths(getSelectionPaths());
		    }
		    super.addSelectionPaths(new TreePath[]{temp});
		}
	    } else {
		super.addSelectionPaths(new TreePath[]{path});
	    }
	}
    }

    // tells whether all siblings of given path are selected.
    private boolean areSiblingsSelected(TreePath path) {
	TreePath parent = path.getParentPath();
	if (parent == null) {
	    return true;
	}
	Object node = path.getLastPathComponent();
	Object parentNode = parent.getLastPathComponent();

	int childCount = model.getChildCount(parentNode);
	for (int i = 0; i < childCount; i++) {
	    Object childNode = model.getChild(parentNode, i);
	    if (childNode == node) {
		continue;
	    }
	    if (!isPathSelected(parent.pathByAddingChild(childNode))) {
		return false;
	    }
	}
	return true;
    }

    /**
     *
     * @param paths
     */
    @Override
    public void removeSelectionPaths(TreePath[] paths) {
	for (int i = 0; i < paths.length; i++) {
	    TreePath path = paths[i];
	    if (path.getPathCount() == 1) {
		super.removeSelectionPaths(new TreePath[]{path});
	    } else {
		toggleRemoveSelection(path);
	    }
	}
    }

    // if any ancestor node of given path is selected then unselect it
    //  and selection all its descendants except given path and descendants.
    // otherwise just unselect the given path
    private void toggleRemoveSelection(TreePath path) {
	Stack stack = new Stack();
	TreePath parent = path.getParentPath();
	while (parent != null && !isPathSelected(parent)) {
	    stack.push(parent);
	    parent = parent.getParentPath();
	}
	if (parent != null) {
	    stack.push(parent);
	} else {
	    super.removeSelectionPaths(new TreePath[]{path});
	    return;
	}

	while (!stack.isEmpty()) {
	    TreePath temp = (TreePath) stack.pop();
	    TreePath peekPath = stack.isEmpty() ? path : (TreePath) stack.peek();
	    Object node = temp.getLastPathComponent();
	    Object peekNode = peekPath.getLastPathComponent();
	    int childCount = model.getChildCount(node);
	    for (int i = 0; i < childCount; i++) {
		Object childNode = model.getChild(node, i);
		if (childNode != peekNode) {
		    super.addSelectionPaths(new TreePath[]{temp.pathByAddingChild(childNode)});
		}
	    }
	}
	super.removeSelectionPaths(new TreePath[]{parent});
    }
}
